package cn.icanci.loopstack.rec.engine.sdk.rule.repository;

import cn.icanci.loopstack.rec.common.aggregation.model.*;
import cn.icanci.loopstack.rec.engine.sdk.properties.RecProperties;
import cn.icanci.loopstack.rec.common.enums.DataSourceTypeEnum;
import cn.icanci.loopstack.rec.common.enums.ScriptTypeEnum;
import cn.icanci.loopstack.rec.engine.script.RecScriptEngine;
import cn.icanci.loopstack.rec.engine.script.RecScriptEngineManager;
import cn.icanci.loopstack.rec.engine.sdk.rule.EngineRepositoryLoader;
import cn.icanci.loopstack.rec.engine.sdk.server.RecNettyServerHandler;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.script.CompiledScript;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * @author icanci
 * @since 1.0 Created in 2022/11/16 08:38
 */
@Service
public class EngineRepositoryHolder implements InitializingBean {
    private static final Logger                        logger                   = LoggerFactory.getLogger(EngineRepositoryHolder.class);

    @Resource
    private EngineRepositoryLoader                     engineRepositoryLoader;

    @Resource
    private RecProperties recProperties;

    private final RecScriptEngine                      recScriptEngine          = RecScriptEngineManager.getRecScriptEngine();

    /** 缓存 key:domainCode value:EngineRepository */
    private static final Map<String, EngineRepository> ENGINE_REPOSITORY_CONFIG = Maps.newConcurrentMap();

    /**
     * 刷新仓储
     *
     * @param domainCodes domainCodes
     */
    public void refresh(Set<String> domainCodes) {
        if (CollectionUtils.isEmpty(domainCodes)) {
            return;
        }
        if (!recProperties.isLoad()) {
            return;
        }
        if (recProperties.isLoadAll()) {
            for (String domainCode : domainCodes) {
                refresh(domainCode);
            }
        } else {
            String domain = recProperties.getDomain();
            if (StringUtils.isBlank(domain)) {
                return;
            }
            Set<String> domains = Sets.newHashSet(domain.replaceAll("\\s*", StringUtils.EMPTY).split(","));
            for (String domainCode : domainCodes) {
                for (String currDomain : domains) {
                    if (StringUtils.equals(domainCode, currDomain)) {
                        refresh(domainCode);
                    }
                }
            }
        }
    }

    /**
     * 刷新仓储
     *
     * @param domain domain
     */
    private void refresh(String domain) {
        try {
            // 1.先获取场景，如果场景都没有，则不处理
            List<SceneDTO.ScenePair> scenePairs = engineRepositoryLoader.loadScenePairs(domain);
            if (CollectionUtils.isEmpty(scenePairs)) {
                return;
            }
            // 2.新建或者获取域对应的仓储
            ENGINE_REPOSITORY_CONFIG.putIfAbsent(domain, new EngineRepository());
            EngineRepository repository = ENGINE_REPOSITORY_CONFIG.get(domain);

            // 3.构建本地缓存
            // 3.1 构建域信息
            DomainDTO domainRepository = engineRepositoryLoader.loadDomain(domain);
            // 3.1.1 构建域信息为空，说明域不存在，不处理
            if (domainRepository == null) {
                logger.warn("[EngineRepositoryHolder][refresh] refresh domain:{} fail,error message: domainRepository is null", domain);
                return;
            }

            // 3.2 构建基础数据信息
            List<BaseDataDTO> baseDatas = engineRepositoryLoader.loadBaseDatas(domain);
            // 3.2.1 构建基础数据信息为空，说明基础数据不存在，不处理
            if (CollectionUtils.isEmpty(baseDatas)) {
                logger.warn("[EngineRepositoryHolder][refresh] refresh domain:{} fail,error message: baseDatas is Empty", domain);
                return;
            }

            // 3.2.1 编译执行脚本
            compileBaseDataScript(baseDatas);
            // 3.2.2 属性仓储信息
            Map<String, BaseDataDTO> refreshBaseDatas = baseDatas.stream().collect(Collectors.toMap(BaseDTO::getUuid, baseData -> baseData));

            // 3.3 构建元数据信息
            List<MetadataDTO> metadatas = engineRepositoryLoader.loadMetadatas(domain);
            Map<String, MetadataDTO> refreshMetadatas = metadatas.stream().collect(Collectors.toMap(BaseDTO::getUuid, metadata -> metadata));

            // 3.4 构建数据源信息
            List<DataSourceDTO> dataSources = engineRepositoryLoader.loadDataSource(domain);
            // 3.4.1 编译执行脚本
            compileDataSourceScript(dataSources);
            // 3.4.2 属性仓储信息
            Map<String, DataSourceDTO> refreshDataSources = dataSources.stream().collect(Collectors.toMap(BaseDTO::getUuid, dataSource -> dataSource));

            // 3.5 构建策略信息
            List<StrategyDTO> strategies = engineRepositoryLoader.loadStrategy(domain);
            Map<DomainSceneKey, StrategyDTO> refreshStrategies = strategies.stream()
                .collect(Collectors.toMap(x -> new DomainSceneKey(x.getDomainCode(), x.getSceneCode()), strategy -> strategy));

            // 3.5 构建策略为空，不处理
            if (CollectionUtils.isEmpty(strategies)) {
                logger.warn("[EngineRepositoryHolder][refresh] refresh domain:{} fail,error message: strategies isEmpty", domain);
                return;
            }

            // 4. 全部构建成功之后，再刷新缓存
            repository.setDomainRepository(domainRepository);
            repository.getBaseDataRepository().putAll(refreshBaseDatas);
            repository.getMetadataRepository().putAll(refreshMetadatas);
            repository.getDataSourceRepository().putAll(refreshDataSources);
            repository.getStrategyRepository().putAll(refreshStrategies);
        } catch (Throwable e) {
            // 有一个刷新失败，则中断启动
            logger.error("[EngineRepositoryHolder][refresh] refresh domain:{} fail,error message:{}", domain, e.getMessage());
            throw e;
        }

    }

    /**
     * 本地缓存刷新，提供自行构建方法
     * 
     * @param domain domain
     * @param refreshBaseDatas refreshBaseDatas
     * @param refreshMetadatas refreshMetadatas
     * @param refreshDataSources refreshDataSources
     * @param refreshStrategies refreshStrategies
     */
    public final void refresh(DomainDTO domain, Map<String, BaseDataDTO> refreshBaseDatas, Map<String, MetadataDTO> refreshMetadatas, Map<String, DataSourceDTO> refreshDataSources,
                              Map<DomainSceneKey, StrategyDTO> refreshStrategies) {
        List<SceneDTO.ScenePair> scenePairs = engineRepositoryLoader.loadScenePairs(domain.getDomainCode());
        if (CollectionUtils.isEmpty(scenePairs)) {
            return;
        }
        ENGINE_REPOSITORY_CONFIG.putIfAbsent(domain.getDomainCode(), new EngineRepository());
        EngineRepository repository = ENGINE_REPOSITORY_CONFIG.get(domain.getDomainCode());
        repository.setDomainRepository(domain);
        repository.getBaseDataRepository().putAll(refreshBaseDatas);
        repository.getMetadataRepository().putAll(refreshMetadatas);
        repository.getDataSourceRepository().putAll(refreshDataSources);
        repository.getStrategyRepository().putAll(refreshStrategies);
    }

    /**
     * 清除缓存操作
     *
     * @param domainCodes domainCodes
     */
    public void clear(Set<String> domainCodes) {
        if (CollectionUtils.isEmpty(domainCodes)) {
            return;
        }
        for (String domainCode : domainCodes) {
            clear(domainCode);
        }
    }

    /**
     * 仓储清除
     * 
     * @param domain domain
     */
    private void clear(String domain) {
        // 1.先获取场景，如果场景都没有，则不处理
        List<SceneDTO.ScenePair> scenePairs = engineRepositoryLoader.loadScenePairs(domain);
        if (CollectionUtils.isEmpty(scenePairs)) {
            return;
        }
        // 2.移除仓储
        ENGINE_REPOSITORY_CONFIG.remove(domain);
    }

    /**
     * 编译执行脚本
     * 
     * @param baseDatas baseDatas
     */
    private void compileBaseDataScript(List<BaseDataDTO> baseDatas) {
        if (CollectionUtils.isEmpty(baseDatas)) {
            return;
        }
        for (BaseDataDTO baseData : baseDatas) {
            CompiledScript compiledScript = recScriptEngine.compile(ScriptTypeEnum.valueOf(baseData.getScriptType()), baseData.getScriptContent());
            baseData.setCompiledScript(compiledScript);
        }
    }

    /**
     * 编译执行脚本
     * 
     * @param dataSources dataSources
     */
    private void compileDataSourceScript(List<DataSourceDTO> dataSources) {
        if (CollectionUtils.isEmpty(dataSources)) {
            return;
        }
        for (DataSourceDTO dataSource : dataSources) {
            DataSourceTypeEnum sourceType = DataSourceTypeEnum.valueOf(dataSource.getDataSourceType());
            if (sourceType == DataSourceTypeEnum.SCRIPT) {
                DataSourceDTO.ScriptInfo scriptInfo = dataSource.getScriptInfo();
                scriptInfo.setCompiledScript(recScriptEngine.compile(ScriptTypeEnum.valueOf(scriptInfo.getScriptType()), scriptInfo.getScriptContent()));
            }
        }
    }

    /**
     * 获取域
     * 
     * @param domainCode domainCode
     * @return 返回域
     */
    public DomainDTO getDomain(String domainCode) {
        return Optional //
            .ofNullable(ENGINE_REPOSITORY_CONFIG.get(domainCode)) //
            .map(EngineRepository::getDomainRepository) //
            .orElse(null);
    }

    /**
     * 获取基础数据
     *
     * @param domainCode domainCode
     * @param uuid uuid
     * @return 返回基础数据
     */
    public BaseDataDTO getBaseData(String domainCode, String uuid) {
        return Optional // 
            .ofNullable(ENGINE_REPOSITORY_CONFIG.get(domainCode)) //
            .map(EngineRepository::getBaseDataRepository) //
            .orElse(Maps.newHashMap()) //
            .get(uuid);
    }

    /**
     * 获取元数据
     *
     * @param domainCode domainCode
     * @param uuid uuid
     * @return 返回元数据
     */
    public MetadataDTO getMetadata(String domainCode, String uuid) {
        return Optional // 
            .ofNullable(ENGINE_REPOSITORY_CONFIG.get(domainCode)) //
            .map(EngineRepository::getMetadataRepository) //
            .orElse(Maps.newHashMap()) //
            .get(uuid);
    }

    /**
     * 获取数据源
     *
     * @param domainCode domainCode
     * @param uuid uuid
     * @return 返回数据源
     */
    public DataSourceDTO getDataSource(String domainCode, String uuid) {
        return Optional // 
            .ofNullable(ENGINE_REPOSITORY_CONFIG.get(domainCode)) //
            .map(EngineRepository::getDataSourceRepository) //
            .orElse(Maps.newHashMap()) //
            .get(uuid);
    }

    /**
     * 获取策略
     *
     * @param domainCode domainCode
     * @param sceneCode sceneCode
     * @return 返回策略
     */
    public StrategyDTO getStrategy(String domainCode, String sceneCode) {
        return Optional // 
            .ofNullable(ENGINE_REPOSITORY_CONFIG.get(domainCode)) //
            .map(EngineRepository::getStrategyRepository) //
            .orElse(Maps.newHashMap()) //
            .get(new DomainSceneKey(domainCode, sceneCode));
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        RecNettyServerHandler.setEngineRepositoryHolder(this);
    }
}
